(defun pr-str (form &optional print-readably)
  (let ((type (mal-type form))
        (value (mal-value form)))
    (cond
     ((eq type 'nil)
      "nil")
     ((eq type 'true)
      "true")
     ((eq type 'false)
      "false")
     ((eq type 'number)
      (number-to-string (mal-value form)))
     ((eq type 'string)
      (if print-readably
          (let ((print-escape-newlines t))
            (prin1-to-string value))
        value))
     ((or (eq type 'symbol) (eq type 'keyword))
      (symbol-name value))
     ((eq type 'list)
      (pr-list value print-readably))
     ((eq type 'vector)
      (pr-vector value print-readably))
     ((eq type 'map)
      (pr-map value print-readably))
     ((eq type 'fn)
      "#<fn>")
     ((eq type 'func)
      "#<func>")
     ((eq type 'atom)
      (format "(atom %s)" (mal-value value))))))

(defun pr-list (form print-readably)
  (let ((items (mapconcat
                (lambda (item) (pr-str item print-readably))
                form " ")))
    (concat "(" items ")")))

(defun pr-vector (form print-readably)
  (let ((items (mapconcat
                (lambda (item) (pr-str item print-readably))
                (append form nil) " ")))
    (concat "[" items "]")))

(defun pr-map (form print-readably)
  (let (pairs)
    (maphash
     (lambda (key value)
       (push (cons (pr-str key print-readably)
                   (pr-str value print-readably))
             pairs))
     form)
    (let ((items (mapconcat
                  (lambda (item) (concat (car item) " " (cdr item)))
                  (nreverse pairs) " ")))
      (concat "{" items "}"))))

(provide 'mal/printer)
